﻿using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using Autofac;
using Autofac.Integration.Mvc;
using BLL;
using BLL.Configuration;
using BLL.Models.Administrators;
using BLL.Security;
using BLL.Services.Administrators;
using BLL.Services.Users;

using Web.Infrastructure;
using Web.Infrastructure.Security;

namespace Web
{
    public class MvcApplication : HttpApplication
    {
        protected void Application_Start()
        {
            // Remove MVC header from http responses
            MvcHandler.DisableMvcResponseHeader = true;

            // Remove unused view engines.
            ViewEngines.Engines.Clear();
            ViewEngines.Engines.Add(new RazorViewEngine());

            var security = new SecurityConfig();
            // Crypto system initialization
            Cryptographer.Initialize(Convert.FromBase64String(security.CryptoKey),
                                     Convert.FromBase64String(security.CryptoIV));
            AreaRegistration.RegisterAllAreas();
            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);

            // Setup IoC container
            var builder = new ContainerBuilder();

            builder.RegisterModule(new BLLModule());
            builder.Register(c => EnvironmentContext()).As<IEnvironmentContext>().InstancePerHttpRequest();
            builder.Register(c => new DefaultGlobalSettings()).As<IGlobalSettings>().SingleInstance();
            builder.RegisterControllers(Assembly.GetExecutingAssembly());
            IContainer container = builder.Build();
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
            var settings = DependencyResolver.Current.GetService<IGlobalSettings>();
            ModelValidatorProviders.Providers.Remove(
                ModelValidatorProviders.Providers.FirstOrDefault(
                    x => x as DataAnnotationsModelValidatorProvider != null));
            ModelValidatorProviders.Providers.Add(new CustomValidationProvider());
            if (settings.Compress.IsEnabledForTemplate)
            {
                var request = (HttpWebRequest)WebRequest.Create(settings.Compress.UrlForStart);
                request.BeginGetResponse(null, request);
            }
        }

        /// <summary>
        /// Handled in order to set our custom identity.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        [SuppressMessage("StyleCopPlus.StyleCopPlusRules",
                         "SP0100:AdvancedNamingRules",
                         Justification = "ASP.NET naming convention")]
        protected void Application_AuthenticateRequest(object sender, EventArgs e)
        {
            var globalSettings = DependencyResolver.Current.GetService<IGlobalSettings>();
            var request = new HttpRequestWrapper(Request);
            AuthenticationCookie userCookie = AuthenticationCookie.GetFromRequest(
                request, FormsAuthentication.FormsCookieName, UserTypes.EndUser);
            AuthenticationCookie adminCookie = AuthenticationCookie.GetFromRequest(
                request, globalSettings.Security.AdminAuthenticationCookieName, UserTypes.Administrator);
            AuthenticationCookie systemCookie = AuthenticationCookie.GetFromRequest(
                request, globalSettings.Security.SystemAuthenticationCookieName, UserTypes.SysAdmin);

            ApplyAuthenticationCookies(new[] { userCookie, adminCookie, systemCookie });
        }

        /// <summary>
        /// Authorizes the account.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        /// <param name="userType">Type of the user.</param>
        /// <param name="assignedRoles">The assigned roles.</param>
        protected bool AuthorizeAccount(string userName,
                                                UserTypes userType,
                                                ref UserRoles assignedRoles)
        {
            var securityService = DependencyResolver.Current.GetService<ISecurityService>();

            return securityService.AuthorizeAccount(userName, userType, ref assignedRoles);
        }

        protected static IEnvironmentContext EnvironmentContext()
        {
            User user = null;
            Administrator administrator = null;
            var isSystem = false;
            if (HttpContext.Current.User.Identity.IsAuthenticated)
            {
                string userName = HttpContext.Current.User.Identity.Name;
                user = DependencyResolver.Current.GetService<IUsersService>().GetUserByEmailOrLogin(userName);
            }

            var principal = HttpContext.Current.User as MultilevelPrincipal;
            if (principal != null)
            {
                if (principal.GetAdministratorIdentity().IsAuthenticated)
                {
                    var name = HttpContext.Current.User.GetAdministratorIdentity().Name;
                    administrator = DependencyResolver.Current
                        .GetService<IAdministratorsService>().GetAdministrator(name);
                }

                if (principal.GetSystemIdentity().IsAuthenticated)
                {
                    isSystem = true;
                }
            }

            return new EnvironmentContext(user, administrator, isSystem);
        }

        protected void ApplyAuthenticationCookies(AuthenticationCookie[] cookies)
        {
            var builder = new MultilevelPrincipalBuilder(new HttpContextWrapper(Context));

            foreach (var cookie in cookies)
            {
                if (null == cookie)
                    continue;

                var assignedRoles = UserRoles.None;
                bool isAuthorized = AuthorizeAccount(cookie.UserName, cookie.UserType, ref assignedRoles);

                if (!isAuthorized)
                {
                    DeleteCookie(cookie.CookieName);
                    continue;
                }

                builder.AssignIdentity(cookie.UserName, cookie.UserType);
                if (UserTypes.EndUser == cookie.UserType)
                    builder.AssignRoles(assignedRoles);
            }
        }

        private void DeleteCookie(string cookieName)
        {
            var responseCookie = Context.Response.Cookies[cookieName];

            if (null != responseCookie)
                responseCookie.Expires = DateTime.Now.AddDays(-1);
        }
    }
}